package eval.engine.cplex;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import eval.deploy.IDemandManager;
import eval.engine.IPlacement;
import eval.engine.cplex.InitialCplexCode.CplexDataHolder;
import eval.host.Host;
import eval.host.IHostManager;
import eval.vm.VMIdentifier;
import eval.vm.VirtualMachine;


public class CplexPlacement implements IPlacement {

	private Map<Host, List<VirtualMachine>> vmMapping = new HashMap<Host, List<VirtualMachine>>();
	private Map<Integer,List<VMIdentifier> > CloudMap = new HashMap<Integer,List<VMIdentifier> >();
	private Map<VirtualMachine, Integer> cpuDemandOfVMs;
	private Map<VirtualMachine, Integer> memDemandOfVMs; 
	private List<VirtualMachine> vmsWithSatisfiedDemand = new ArrayList<VirtualMachine>();
	private List<VirtualMachine> vmsWithUNSatisfiedDemand = new ArrayList<VirtualMachine>();
	private List<VirtualMachine> VMSrequestedTobedeployed;
	private List<VirtualMachine> RejectedVMS;
	private List<VirtualMachine> PlacedVMS;
	private List<Host> turendONHosts = new ArrayList<Host>();
	private List<Host> turendOFFHosts = new ArrayList<Host>();
	private List<Integer> OpenHosts = new ArrayList<Integer>();
	private int[] hostLoadAtPlacement;
	private List<VirtualMachine> removedList;
	
	private IPlacement lastPlacement; 
	private IDemandManager demandManager;
	private IHostManager hostManager;
	private CplexDataHolder cplexData;
	private CplexMapIndexToVmId mapIndexToVmId;
	

	private int tick;
	private boolean emptyPlacement = false;
	
	private int totalCloudCpuLoad = 0;
	private int totalCloudCpuDemand = 0;
	private int totalRequestedCpu = 0;
	private int totalSatisfiedDemand = 0;
	private int totalUnSatisfiedDemand = 0;
	private int totalCpuCapcityAtCloud = 0;
	private int totalCpuCapcityAtOpenHosts = 0;
	private double sumOfMinREservtion = 0.0;
	private int totalCloudMemAtCloud = 0;
	private int totalCloudMemDemand = 0;
	private int totalMemCapcityAtOpenHosts = 0;
	private int numberOfOpenHosts = 0;
	private int numberOfCloseHosts = 0;
	private int numberOfCandidateHosts = 0;
	private int numberOfVmsAtCloud = 0;
	private double hostsStartUpCost  = 0.0;
	private double baseHostCost = 0.0;
	private int numOfDeployVm = 0;
	private int removedVm = 0;
	private int numberOfRelocatedVm = 0;
	private int numberOfVmRequestedTobedeployed = 0;;
	
	private double minCpuLoadInHosts = Double.POSITIVE_INFINITY;
	private double maxCpuLoadInHosts = Double.NEGATIVE_INFINITY;
	private double minMemLoadInHosts = Double.POSITIVE_INFINITY;
	private double maxMemLoadInHosts = Double.NEGATIVE_INFINITY;

	
	
	
	
	
	
	
	

	public CplexPlacement(Map<Host, List<VirtualMachine>> vmMapping_, IHostManager hostManager_,
			IPlacement lastPlacement_, IDemandManager demandManager_  , List<VirtualMachine> removedList_
			,List<VirtualMachine> vmsToDeploy_,List<VirtualMachine> PlacedVMS_ ,List<VirtualMachine> RejectedVMS_ 
			,List<VirtualMachine> relocatedVms_, int tick_,
			CplexDataHolder data_,CplexMapIndexToVmId mapIndexToVmId_) {

		this.lastPlacement = lastPlacement_;
		this.vmMapping = vmMapping_;
		this.hostManager = hostManager_;
		this.removedList = removedList_;
		this.VMSrequestedTobedeployed = vmsToDeploy_;
		this.PlacedVMS = PlacedVMS_;
		this.RejectedVMS = RejectedVMS_;
		this.demandManager = demandManager_;
		if(VMSrequestedTobedeployed.size() != (PlacedVMS.size() +RejectedVMS.size()))
			System.out.println("Error at tick: "+tick);
		this.tick = tick_;
		this.cpuDemandOfVMs = demandManager.getCPUDemandOfAllVMsAtTick(tick);
		this.memDemandOfVMs = demandManager.getMEMDemandOfAllVMsAtTick(tick);
		this.hostLoadAtPlacement = new int[hostManager.getNumberOfHosts()];
		this.cplexData = new CplexDataHolder(data_.getPlacementMap(),data_.getActiveHostsMap());
		this.mapIndexToVmId = new CplexMapIndexToVmId(mapIndexToVmId_);
		
		
		updateCloudMap(vmMapping_);
		updatePlacement();
		
		numOfDeployVm = PlacedVMS_.size();
		removedVm = removedList_.size();
		numberOfRelocatedVm = relocatedVms_.size();
		numberOfVmRequestedTobedeployed = vmsToDeploy_.size();
	}




	

	
	private void updatePlacement(){
		for (Host host : vmMapping.keySet()) {
			hostManager.updateCurrentHostDemand(demandManager, host, vmMapping.get(host), tick);
		}
/*
		double sumOfemMAtHost = 0;
		// iterates over hosts
		for (Host host : vmMapping.keySet()) {
			hostManager.updateCurrentHostDemand(demandManager, host, vmMapping.get(host), tick);
			sumOfemMAtHost = 0;
			hostLoadAtPlacement[host.getHostId()] = host.getCurrentCpuLoad();

			if(host.isOpen()){
				numberOfOpenHosts++;}
			else if(host.isClose()){
				numberOfCloseHosts++;}
			else{numberOfCandidateHosts++;}

			if(host.getCurrentCpuLoad() < minCpuLoadInHosts){
				minCpuLoadInHosts = host.getCurrentCpuLoad();
			}
			if(host.getCurrentCpuLoad() > maxCpuLoadInHosts){
				maxCpuLoadInHosts = host.getCurrentCpuLoad();
			}

			// iterates over vms within the host
			for (VirtualMachine vm : vmMapping.get(host)) {
				sumOfMinREservtion+= vm.getVMConfiguration().getMinCpu();
				numberOfVmsAtCloud++;

				totalCloudCpuLoad += vm.getSatisfiedCpuDemand();

				totalCloudCpuDemand +=  cpuDemandOfVMs.get(vm);//  demandManager.getCPUDemandOfVmAtTick(vm,tick);

				totalCloudMemDemand += memDemandOfVMs.get(vm);

				sumOfemMAtHost += memDemandOfVMs.get(vm);

				int CurVmCons = vm.getSatisfiedCpuDemand();
				if(CurVmCons == cpuDemandOfVMs.get(vm) ||
						CurVmCons < vm.getVMConfiguration().getMinCpu()){
					vmsWithSatisfiedDemand.add(vm);
					totalSatisfiedDemand += CurVmCons;
					int unSatCpuChk = cpuDemandOfVMs.get(vm).intValue()-vm.getSatisfiedCpuDemand();
					if(unSatCpuChk>0)
						totalUnSatisfiedDemand += unSatCpuChk;
				}
				else{
					vmsWithUNSatisfiedDemand.add(vm);
					totalUnSatisfiedDemand += cpuDemandOfVMs.get(vm) - CurVmCons;
					int unSatCpuChk = vm.getSatisfiedCpuDemand();
					if(unSatCpuChk>0)
						totalSatisfiedDemand += unSatCpuChk;
				}


			}//end: iterates over vms within the host

			if(sumOfemMAtHost < minMemLoadInHosts){
				minMemLoadInHosts = sumOfemMAtHost;
			}
			if(sumOfemMAtHost > maxMemLoadInHosts){
				maxMemLoadInHosts = sumOfemMAtHost;
			}

		}//end: iterates over hosts
		*/
		setTotalCpuCapcityAtCloud();
		setTotalCpuCapcityAtOpenHosts();
		setTotalCloudCpuLoad();
		setTotalCpuDemand();
		setTotalRequestedCpu();
		setTotalSatisfiedDemand();
		setTotalUnSatisfiedDemand();
		setSumOfMinREservtion();
		setTotalCloudMemAtCloud();
		setTotalCloudMemDemand();
		setTotalMemCapcityAtOpenHosts();
		setNumberOfOpenHosts();
		setNumberOfCloseHosts();
		setNumberOfCandidateHosts();
		setNumberOfVmsAtCloud();
		setTurendONHosts();
		setTurendOFFHosts();
		setHostsStartUpCost();
		setBaseHostCost();
		
		if(totalCloudCpuLoad==0)
			System.out.println("sd");
		
		
		
	}

	public void setTotalCpuCapcityAtCloud() {
		int total = 0;
		List<Host> hosts =  hostManager.getHosts();
		for(Host it : hosts) {
			total += it.getHostCapacity().getCpuCapacity();
		}
		totalCpuCapcityAtCloud = total;
	}
	
	public void setTotalCpuCapcityAtOpenHosts() {
		int total = 0;
		for(Host host : vmMapping.keySet() ){
			if(host.isOpen())
				total += host.getHostCapacity().getCpuCapacity();
		}
		totalCpuCapcityAtOpenHosts = total;
	}
	
	private void setTotalCloudCpuLoad() {
		int load = 0;
		for(Host host : vmMapping.keySet() ){
//			if(host.isOpen())
			//TODO: Amit fix free cpu at open host. 
			for (VirtualMachine vm : vmMapping.get(host)) {
				load += vm.getSatisfiedCpuDemand();
				//load += host.getCurrentCpuLoad();
			}
		}
		totalCloudCpuLoad = load;
	}
	
	private void setTotalCpuDemand() {
		int demand = 0;
		for(Host host : vmMapping.keySet() ){
			for(VirtualMachine vm : vmMapping.get(host)) {
				demand += demandManager.getCPUDemandOfVmAtTick(vm, tick);
			}
		}
		totalCloudCpuDemand = demand;
	}
	
	public void setTotalRequestedCpu () {
		int sum = 0;
		//the Requested CPU include the cpu demand of vms that have been rejected,so we must add them to the calculation.
		if(!RejectedVMS.isEmpty()){
			for(VirtualMachine vm : RejectedVMS){
				sum += demandManager.getCPUDemandOfVmAtTick(vm, tick);
			}
		}
		sum += totalCloudCpuDemand;
		totalRequestedCpu = sum;
	}
	
	private void setTotalSatisfiedDemand() {
		int demand = 0;
		for(Host host : vmMapping.keySet() ){
			for(VirtualMachine vm :vmMapping.get(host)) {
				demand += vm.getSatisfiedCpuDemand();
			}
		}
		totalSatisfiedDemand = demand;
	}
	
	private void setTotalUnSatisfiedDemand() {
		int reqDemand = 0;
		int unSatDemand = 0;
		for(Host host : vmMapping.keySet() ){
			for(VirtualMachine vm :vmMapping.get(host)) {
				unSatDemand += (demandManager.getCPUDemandOfVmAtTick(vm, tick)-vm.getSatisfiedCpuDemand());
			}
		}
		totalUnSatisfiedDemand = unSatDemand;
	}
	
	private void setSumOfMinREservtion() {
		double minRes = 0.0;
		for(Host host : vmMapping.keySet() ){
			for(VirtualMachine vm : vmMapping.get(host)) {
				minRes += vm.getVMConfiguration().getMinCpu();
			}
		}
		sumOfMinREservtion = minRes;
		
	}
	
	public void setTotalCloudMemAtCloud() {
		int total = 0;
		List<Host> hosts =  hostManager.getHosts();
		for(Host it : hosts) {
			total += it.getHostCapacity().getMemCapacity();
		}
		totalCloudMemAtCloud = total;
	}
	
	private void setTotalCloudMemDemand() {
		int mem = 0;
		for(Host host : vmMapping.keySet() ){
			for(VirtualMachine vm : vmMapping.get(host)) {
				mem += demandManager.getMemDemandOfVmAtTick(vm, tick);
			}
		}
		totalCloudMemDemand = mem;
	}
	
	public void setTotalMemCapcityAtOpenHosts() {
		int total = 0;
		List<Host> hosts =  hostManager.getOpenHosts();
		for(Host it : hosts) {
			total += it.getHostCapacity().getMemCapacity();
		}
		totalMemCapcityAtOpenHosts = total;
	}
	
	private void setNumberOfOpenHosts() {
		numberOfOpenHosts = hostManager.getOpenHosts().size();
	}
	
	private void setNumberOfCloseHosts() {
		numberOfCloseHosts = hostManager.getHosts().size()-	numberOfOpenHosts;
	}
	
	private void setNumberOfCandidateHosts() {
		int canToClose = 0;
		for(Host host :hostManager.getOpenHosts()) {
			if(host.isCandidateForClosing())
				canToClose++;
		}
		numberOfCandidateHosts = canToClose;
	}
	
	private void setNumberOfVmsAtCloud() {
		int count = 0;
	for(Host host : vmMapping.keySet() ){
		for(VirtualMachine vm : vmMapping.get(host)) {
			count ++;
		}
			
	}
	numberOfVmsAtCloud = count;
	}
	
	private void setTurendONHosts() {
		if(tick == 0) return;
		for(Host curHost : 	vmMapping.keySet() ){
			if(curHost.isOpen() || curHost.isCandidateForClosing() ){
				OpenHosts.add(curHost.getHostId());
				if(!lastPlacement.getOpenHosts().contains(curHost.getHostId())){
					turendONHosts.add(curHost);
				}
			}
		}
	}

	private void setTurendOFFHosts() {
		if(tick == 0) return;
		for(Host curHost : 	vmMapping.keySet() ){
			if(lastPlacement.getOpenHosts().contains(curHost.getHostId()) && (!this.getOpenHosts().contains(curHost.getHostId()))){
				turendOFFHosts.add(curHost);
			}
		}
	}
	
	public void setHostsStartUpCost() {
		double cost = 0;
		List<Host> hosts =  getTurnedONHosts();
		for(Host it : hosts) {
			cost += hostManager.getStartUpCostForHost(it.getHostCapacityEnum());
		}
		hostsStartUpCost = cost;
	}
	
	public void setBaseHostCost() {
		double cost = 0;
		List<Host> hosts =  hostManager.getOpenHosts();
		for(Host it : hosts) {
			cost += hostManager.getBaseCostForHost(it.getHostCapacityEnum());
		}
		baseHostCost = cost;
	}

	private void updateCloudMap(Map<Host, List<VirtualMachine>> vmMAP){
		for(Host host : vmMAP.keySet()){
			CloudMap.put(host.getHostId(), new ArrayList<VMIdentifier>());

			for(VirtualMachine vm : vmMAP.get(host)){
				CloudMap.get(host.getHostId()).add(vm.getIdentifier());
			}
		}

	}
	

	@Override
	public CplexMapIndexToVmId getCplexMapIndexToVmId() {
		return mapIndexToVmId;
	}

	@Override
	public CplexDataHolder getCplexData() {
		return cplexData;
	}

	@Override
	public int getTotalRequestedCPU() {
		return totalRequestedCpu ;
	}

	@Override
	public List<Integer> getOpenHosts(){
		return OpenHosts;
	}

	@Override
	public int getTotalCloudCPULoad() {
		return totalCloudCpuLoad;
	}

	@Override
	public int getTotalCloudCpuDemand() {
		return totalCloudCpuDemand;
	}

	@Override
	public int getTotalCloudMemDemand() {
		return totalCloudMemDemand;
	}

	@Override
	public List<Host> getTurnedONHosts() {
		return turendONHosts;
	}

	@Override
	public List<Host> getTurnedOFFHosts(){
		return turendOFFHosts;
	}


	@Override
	public int getNumberOfOpenHosts() {
		return numberOfOpenHosts;
	}

	@Override
	public int getNumberOfClosedHosts() {
		return numberOfCloseHosts;
	}

	@Override
	public int getNumberOfCandidatesForClosingHosts() {
		return numberOfCandidateHosts;
	}

	@Override
	public int getNumberOfTurnOffHosts() {
		return turendOFFHosts.size();
	}

	@Override
	public int getNumberOfTurnOnHosts() {
		return turendONHosts.size();
	}

	@Override
	public double getMinCPULoadInHosts() {
		return minCpuLoadInHosts;
	}

	@Override
	public double getMinMEMLoadInHosts() {
		return minMemLoadInHosts;
	}

	@Override
	public double getAvgCPULoadInHosts() {
		return (totalCloudCpuLoad / hostManager.getNumberOfHosts());
	}

	@Override
	public double getAvgMEMLoadInHosts() {
		return (totalCloudMemDemand / hostManager.getNumberOfHosts());
	}

	@Override
	public double getMaxCPULoadInHosts() {
		return maxCpuLoadInHosts;
	}

	@Override
	public double getMaxMEMLoadInHosts() {
		return maxMemLoadInHosts;
	}

	@Override
	public List<VirtualMachine> getVMSrequestedTobedeployed() {
		return VMSrequestedTobedeployed;
	}
	
//	@Override
//	public int getNumOfVmsRequestTobedeployed() {
//		return VMSrequestedTobedeployed;
//	}

	@Override
	public List<VirtualMachine> getVMSWithSatisfiedDemand() {
		return vmsWithSatisfiedDemand;
	}

	@Override
	public List<VirtualMachine> getVMSWithUNSatisfiedDemand() {
		return vmsWithUNSatisfiedDemand;
	}

	@Override
	public List<VirtualMachine> getRejectedVMS() {
		return RejectedVMS;
	}

	@Override
	public List<VirtualMachine> getRemovedList() {
		return removedList;
	}

	@Override
	public List<VirtualMachine> getPlacedList() {
		return PlacedVMS;
	}

	@Override
	public int getNumberOfVMsAtCloud() {
		return numberOfVmsAtCloud;
	}

	@Override
	public int getNumberOfVMsWithSatisfiedDemand() {
		return vmsWithSatisfiedDemand.size();
	}

	@Override
	public int getNumberOfVMsWithUnSatisfiedDemand() {
		return vmsWithUNSatisfiedDemand.size();
	}

	@Override
	public int getTotalSatisfiedCPUDemand() {
		return totalSatisfiedDemand;
	}

	@Override
	public int getTotalUnSatisfiedCPUDemand() {
		return totalUnSatisfiedDemand;
	}

	@Override
	public int getNumberOfRemovedVMs() {
		return removedVm;
	}

	@Override
	public int getNumberOfPlacedVMs() {
		return numOfDeployVm;
	}

	@Override
	public boolean isEmptyPlacement() {
		return emptyPlacement;
	}

	@Override
	public Map<Host, List<VirtualMachine>> getvmMapping(){
		return this.vmMapping;
	}

	@Override
	public Map<Integer, List<VMIdentifier>> getCloudMap(){
		return this.CloudMap;
	}

	@Override
	public boolean isHostOpen(Host host){
		for(Host iterHost : vmMapping.keySet()){
			if(iterHost.getHostId() == host.getHostId() && iterHost.isOpen())
				return true;
		}
		return false;
	}

	@Override
	public boolean isHostClose(Host host){
		for(Host iterHost : getvmMapping().keySet()){
			if(iterHost.getHostId() == host.getHostId() && iterHost.isClose())
				return true;
		}
		return false;
	}

	@Override
	public Host getHostFromLastPlacement(Host host){
		for(Host iterHost : lastPlacement.getvmMapping().keySet()){
			if(iterHost.getHostId() == host.getHostId() )
				return iterHost;
		}
		return null;
	}

	@Override
	public int getNumberOfVmsReqToBeDeploy() {
		return getVMSrequestedTobedeployed().size();
	}

	@Override
	public int getTotalCpuCapcityAtOpenHosts() {
		return totalCpuCapcityAtOpenHosts;
	}

	@Override
	public int getTotalCpuCapcityAtCloud() {
		return totalCpuCapcityAtCloud;
	}

	@Override
	public int getTotalMemCapcityAtOpenHosts() {

		return totalMemCapcityAtOpenHosts;
	}

	@Override
	public int getTotalCloudMemAtCloud() {
		return totalCloudMemAtCloud;
	}

	@Override
	public double getBaseHostCost() {
		return baseHostCost;
	}

	@Override
	public double getHostsStartUpCost() {
		return hostsStartUpCost;
	}

	@Override
	public double getWorkingHostCost() {
		return hostManager.getLoadCost(totalCloudCpuLoad);
		//TODO: Amit
		//return hostManager.getLoadCost(numberOfOpenHosts);
	}

	@Override
	public double getIncomeForSatisfyDemand() {
		return hostManager.getIncomeOnLoad(totalCloudCpuLoad);
	}

	@Override
	public int getTotalMemLoadAtCloud() {
		return totalCloudMemAtCloud;
	}
	@Override
	public double getSumOfMinReservtion() {
		return  sumOfMinREservtion;
	}

	@Override
	public int getVmRelocate() {
		return numberOfRelocatedVm;
	}


}